﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using MentalAlchemy.Atomics;
using MentalAlchemy.Molecules;
using MentalAlchemy.Molecules.MachineLearning;
using MentalAlchemy.Molecules.NevaAlgorithm;
using MentalAlchemy.Molecules.NevaAlgorithm.Compounds;
using MentalAlchemy.Molecules.NevaAlgorithm.nevamod;

namespace NevaAlg
{
	public partial class MainForm : Form
	{
		public MainForm()
		{
			InitializeComponent();
		}

		private void TestXorBtn_Click(object sender, EventArgs e)
		{
			var pars = nevaPropertiesControl1.Parameters;
			pars.FitnessFunction = neuralProblemSelectorControl1.SelectedProblem;

			var bests = new List<NevaInd> ();
			var stats = NevaElements.Run(pars, (int)RunsNumeric.Value, out bests);

			using (var writer = new StreamWriter(GetLogfileName(pars)))
			{
				writer.WriteLine("> Algorithm parameters:");
				pars.Print(writer);
				writer.WriteLine("\n> Averaged run data (over {0} runs):", RunsNumeric.Value);
				FileIO.WriteStatsNumerate(writer, stats, false);

				//
				// test found best solutions.
				var ers = new List<float>();
				foreach (var ind in bests)
				{
					ind.Network.BuildNetwork(ind.Edges);
					var fit = pars.FitnessFunction.Test(ind.Network);
					ers.Add(fit.Value);
				}

				//
				// write ers.
				writer.WriteLine("\n> Test errors:");
				for (int i = 0; i < ers.Count; i++)
				{
					writer.WriteLine(string.Format("{0}:\t{1}", (i+1), ers[i]));
				}

				writer.WriteLine("\n> Test errors stats:");
				var erStats = VectorMath.CalculateStats(ers.ToArray());
				writer.WriteLine(erStats.GetStatsHeader());
				writer.WriteLine(erStats.GetStatsString());
			}
		}

		private string GetLogfileName (NevaParameters pars)
		{
			var res = string.Format("{0}_{1}_{2}_{3}_{4}.log", neuralProblemSelectorControl1.SelectedProblemName, pars.UseElitism, pars.NodesWindowSize, pars.NodesMutationLockTime, pars.NodesMutationLockThreshold);
			return res;
		}

		private void TestNeva2Btn_Click(object sender, EventArgs e)
		{
			var pars = nevaPropertiesControl1.Parameters;
			pars.FitnessFunction = neuralProblemSelectorControl1.SelectedProblem;
			pars.UseParentChildCompetition = false;

			List<Neva2Ind> bests;	// = new List<Neva2Ind>();
			//List<List<NevaIndChanges>> chs;	// = new List<Neva2Ind>();
			var stats = Neva2Elements.Run(pars, (int)RunsNumeric.Value, out bests, LogCheck.Checked);

			WriteLog(pars, stats, bests, null);
		}

		private void Neva2SingleRunBtn_Click(object sender, EventArgs e)
		{
			var pars = nevaPropertiesControl1.Parameters;
			pars.FitnessFunction = neuralProblemSelectorControl1.SelectedProblem;
			pars.UseParentChildCompetition = false;

			var neva = new Neva2();
			neva.EnableLogging = LogCheck.Checked;
			neva.Run(pars);

			var bests = new List<Neva2Ind>();
			bests.Add((Neva2Ind)neva.BestIndividual);

			WriteLog(pars, neva.FitnessStats, bests, (Neva2Contents)neva.Contents);

			// take the first best individual and analyze its changes.
			var opsAnalyzer = new OperatorsAnalyzer();
			opsAnalyzer.Changes = bests[0].Changes;
			var chanForm = OperatorsAnalyzerForm.Create();
			chanForm.OperatorsAnalyzerControl.OpsAnalyzer = opsAnalyzer;
			chanForm.Size = new Size(640,480);
			chanForm.StartPosition = FormStartPosition.CenterScreen;

			chanForm.ShowDialog();
		}

		public void WriteLog(NevaParameters pars, List<Stats> stats, List<Neva2Ind> bests, Neva2Contents conts)
		{
			using (var writer = new StreamWriter(GetLogfileName(pars)))
			{
				writer.WriteLine("> Algorithm parameters:");
				pars.Print(writer);
				writer.WriteLine("\n> Averaged run data (over {0} runs):", RunsNumeric.Value);
				FileIO.WriteStatsNumerate(writer, stats, false);

				//
				// test found best solutions.
				var ers = new List<float>();
				var ersTrain = new List<float>();
				var ersTest = new List<Fitness>();
				foreach (var ind in bests)
				{
					//ind.Network.BuildNetwork(ind.Edges);
					//ind.BuildNetwork();
					var fitTrain = pars.FitnessFunction.Calculate(ind.Network);
					ind.BuildNetwork();
					fitTrain = pars.FitnessFunction.Calculate(ind.Network);
					var fit = pars.FitnessFunction.Test(ind.Network);
					ersTrain.Add(fitTrain.Value);
					ersTest.Add(fit);
					ers.Add(fit.Value);
				}

				//
				// write ers.
				writer.WriteLine("\n> Training & Test errors:");
				for (int i = 0; i < ers.Count; i++)
				{
					writer.WriteLine(string.Format("{0}:\t{1}\t{2}", (i + 1), ersTrain[i], ersTest[i]));
				}

				writer.WriteLine("\n> Test errors stats:");
				var erStats = VectorMath.CalculateStats(ers.ToArray());
				writer.WriteLine(erStats.GetStatsHeader());
				writer.WriteLine(erStats.GetStatsString());

				//
				// write the best solution info.
				var nodes = new int[bests.Count];
				var conns = new int[bests.Count];
				for (int i = 0; i < bests.Count; i++)
				{
					var ind = bests[i];
					writer.WriteLine(string.Format("\n> The best solution #{0} ({1})", i, ind.Fitness));
					writer.WriteLine(string.Format("Hidden nodes and connections:\t{0}\t{1}", ind.Network.HiddenNodesCount, ind.Edges.Count));
					var chStr = Neva2Elements.ConvertToStrings(ind.Changes);
					foreach (var str in chStr)
					{
						writer.WriteLine(str);
					}

					nodes[i] = ind.Network.HiddenNodesCount;
					conns[i] = ind.Edges.Count;
				}
				// write structure stats.
				writer.WriteLine("\n> The best solutions structure stats (nodes, connections)");
				writer.WriteLine(VectorMath.CalculateStats(nodes).GetStatsString());
				writer.WriteLine(VectorMath.CalculateStats(conns).GetStatsString());


				//
				// write operators stats.

				// write operators counters.
				if (conts != null)
				{
					writer.WriteLine("\n> Mutations stats:");
					foreach (var count in conts.MutationCounters)
					{
						writer.WriteLine(count.Key + "\t" + count.Value);
					}
				}

				#region - Logging for transitions matrix. -
				//
				// compute mean and var for trans matrix.
				var transVs = new List<float[]>();
				var transMs = new List<float[,]>();
				var ops = Operators.GetMutOperators();
				foreach (var ind in bests)
				{
					var trans = OperatorsAnalyzer.ComputeSuccessfulTransitions(ind.Changes, FitnessComparator.MinimizeFitness);
					var trM = OperatorsAnalyzer.TransitionsToMatrix(trans, ops);
					transMs.Add(trM);
					var tempV = MatrixMath.ConvertToVector(trM);
					transVs.Add(tempV);
				}

				var transMean = VectorMath.MeanVector(transVs);
				var transVar = VectorMath.VarianceVector(transVs);
				var transM = MatrixMath.CreateFromVector(transMean, Operators.MUTATION_TYPE_COUNT);
				var transMVar = MatrixMath.CreateFromVector(transVar, Operators.MUTATION_TYPE_COUNT);

				// write resulting matrices.
				writer.WriteLine("\n> Operators' default order");
				FileIO.WriteAllLines(writer, ops);
				FileIO.WriteColumns(writer, transM, "\n> Matrix of means for succesfull transitions", '\t');
				FileIO.WriteColumns(writer, transMVar, "\n> Matrix of variances for succesfull transitions", '\t');

				//
				// write individual transition matrices
				for (int i = 0; i < transMs.Count; i++)
				{
					writer.WriteLine("\n> Matrix of succesfull transitions #" + i);
					FileIO.WriteColumns(writer, transMs[i], null, '\t');
				} 
				#endregion
			}	// end of log-writing.
		}

		private void TestCloneBtn_Click(object sender, EventArgs e)
		{
			var pars = nevaPropertiesControl1.Parameters;
			pars.FitnessFunction = neuralProblemSelectorControl1.SelectedProblem;
			pars.UseParentChildCompetition = false;
			pars.Algorithm = new Neva2();
			pars.Algorithm.Contents.Init(pars);

			LogWriter.Instance().Enabled = true;
			Test.TestIndsCloning(pars, pars.FitnessFunction);

			LogWriter.Instance().Enabled = LogCheck.Checked;
		}

		/// <summary>
		/// 0. Init alg. (using trans. matrix)
		/// 1. Perform run
		/// 2. Get trans. matrix.
		/// 3. 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void IncTestBtn_Click(object sender, EventArgs e)
		{
			// test increamental learning.
			int INC_RUNS = (int)RunsNumeric.Value;
			var ops = Operators.GetMutOperators();

			var pars = nevaPropertiesControl1.Parameters;
			pars.FitnessFunction = neuralProblemSelectorControl1.SelectedProblem;
			pars.UseParentChildCompetition = false;

			var allTrans = new List<float[,]>();
			var allBests = new List<Neva2Ind>();
			var allFitStats = new List<List<Stats>>();
			float[,] transM = LoadDefaultTransMatrix ();
			for (int i = 0; i < INC_RUNS; i++)
			{
				var neva = new Neva2();
				neva.EnableLogging = LogCheck.Checked;
				neva.TransMatrix = transM;
				neva.Run(pars);

				var best = neva.BestIndividual;

				// get ops. stats.
				var tempTrans = OperatorsAnalyzer.ComputeSuccessfulTransitions(((Neva2Ind) best).Changes, FitnessComparator.MinimizeFitness);
				transM = OperatorsAnalyzer.TransitionsToMatrix(tempTrans, ops);

				// store stats.
				//allTrans.Add(neva.TransMatrix);
				allTrans.Add(transM);
				allBests.Add((Neva2Ind)neva.BestIndividual);
				allFitStats.Add(neva.FitnessStats);

				// write ind run logs.
				var filename = pars.FitnessFunction.Name + "_" + (i + 1) + ".log";
				WriteIncLog(filename, allFitStats[i], allBests[i], allTrans[i]);
			}
			allTrans.Add(transM);

			//// write ind run logs.
			//for (int i = 0; i < INC_RUNS; i++)
			//{
			//    var filename = pars.FitnessFunction.Name + "_" + (i + 1) + ".log";
			//    WriteIncLog(filename, allFitStats[i], allBests[i], allTrans[i]);
			//}

			// write log for trans matrices.
			var translogfile = pars.FitnessFunction.Name + "_trans.log";
			WriteTransLog(translogfile, pars, allBests, allTrans);
		}

		private static float[,] LoadDefaultTransMatrix()
		{
			if (!File.Exists(Neva2.DEFAULT_TRANSMATRIX_FILE)) return null;

			var lines = FileIO.ReadAllLines(Neva2.DEFAULT_TRANSMATRIX_FILE);
			var tempM = new List<float[]>();
			for (int i = 0; i < Operators.MUTATION_TYPE_COUNT; i++)
			{
				var tempStr = lines[i].Split('\t');
				tempStr = Strings.RemoveEmptyElements(tempStr);
				tempM.Add(VectorMath.CreateFromStringsArray(tempStr));
			}
			var res = MatrixMath.CreateFromRowsList(tempM);

			return res;
		}

		private void WriteIncLog (string filename, List<Stats> fitStats, Neva2Ind best, float[,] transM)
		{
			const char SEP = '\t';

			var lines = new List<string>();
			lines.AddRange(StructMath.ConvertToStringsList(fitStats, true));
			lines.Add("\n> The best solution changes:");
			lines.AddRange(Neva2Elements.ConvertToStrings(best.Changes));

			lines.Add("\n> Operators usage:");
			var data = new List<int[]>();
			var genData = OperatorsAnalyzer.GetGenerationsData(best.Changes);
			var header = "";
			foreach (var gData in genData)
			{
				data.Add(gData.Value);
				header += gData.Key + SEP;
			}
			lines.Add(header);
			var size = data[0].Length;	// find max row length.
			for (int i = 1; i < data.Count; i++) { if (size < data[i].Length) { size = data[i].Length; } }
			for (int i = 0; i < size; i++)
			{
				var str = "";
				for (int j = 0; j < data.Count; j++)
				{
					if (data[j].Length > i)
					{
						str += data[j][i];
					}
					str += SEP;
				}
				lines.Add(str);
			}

			lines.Add("\n> Operators utility:");
			var opStats = OperatorsAnalyzer.CalculateFitnessDeltaStats(best.Changes);
			foreach (var stat in opStats)
			{
				lines.Add(stat.Key + SEP + stat.Value.GetStatsString());
			}

			lines.Add("\n> Transitions matrix:");
			lines.AddRange(MatrixMath.ConvertToRowsStringsList(transM, SEP));
			File.WriteAllLines(filename, lines.ToArray());
		}

		private void WriteTransLog (string filename, NevaParameters pars, List<Neva2Ind> bests, List<float[,]> transMs)
		{
			var lines = new List<string>();

			lines.AddRange(pars.ToStrings());

			lines.Add("\n> Best fitness change over runs:");
			for (int i = 0; i < bests.Count; i++)
			{
				var ind = bests[i];
				var test = pars.FitnessFunction.Test(ind.Network);
				lines.Add(string.Format("{0}:\t{1}\t{2}", (i+1), ind.Fitness, test));
			}

			lines.Add("\n> Transition matrices:");
			for (int i = 0; i < bests.Count; i++)
			{
				lines.Add(string.Format("{0}:", (i + 1)));
				lines.AddRange(MatrixMath.ConvertToRowsStringsList(transMs[i], '\t'));
				lines.Add("");
			}

			File.WriteAllLines(filename, lines.ToArray());
		}

		private void TestExportBtn_Click(object sender, EventArgs e)
		{
			var pars = nevaPropertiesControl1.Parameters;
			pars.FitnessFunction = neuralProblemSelectorControl1.SelectedProblem;
			pars.UseParentChildCompetition = false;
			pars.Algorithm = new Neva2();
			pars.Algorithm.Contents.Init(pars);

			LogWriter.Instance().Enabled = true;
			Test.TestDotExport(pars);

			LogWriter.Instance().Enabled = LogCheck.Checked;
		}
	}
}